#region Using directives

using System;
using System.Collections.Generic;
using System.Text;

using Weazel.Gezel.Model.Errors;

#endregion

namespace Weazel.Gezel.Model
{
  /// <summary>
  /// Extends the model object by providing verification capabilities.
  /// </summary>
  public partial class system : IVerifiable
  {
    /// <summary>
    /// Verifies a gezel system component.
    /// </summary>
    /// <param name="errors">the error list</param>
    /// <param name="arguments">optional custom verification arguments</param>
		public virtual void Verify(ref List<VerificationError> errors, ref List<VerificationWarning> warnings, params object[] arguments)
    {
      // build list of system nets
      foreach (Stimulus stim in stimuli.Values)
      {
        nets[stim.SignalRef] = new Net(this, stim);
      }

			// add 'unconnected' datapaths (or Ip block)
			foreach (string datapathName in UnconnectedDatapaths)
			{
				if (parent.Datapaths.ContainsKey(datapathName))
				{
					Datapath datapath = parent.Datapaths[datapathName];
					datapath.InSystem = true;
				}
				else if (parent.IPBlocks.ContainsKey(datapathName))
				{
					IPBlock ipBlock = parent.IPBlocks[datapathName];
					ipBlock.InSystem = true;
				}
				else
				{
					ErrorMessage message = new ErrorMessage();
					message.AddString("Unknown datapath or IP Block '" + datapathName + "' used in system '");
					message.AddEntity(this);
					message.AddString("' at ");
					message.AddPosition(StartPosition);

					errors.Add(new VerificationError(VerificationErrorType.SystemUnknownDatapathOrIpBlock, message));
				}
			}

			foreach (system.DatapathConnectionInfo connection in PortConnections.Values)
			{				
				// there should atleast be one connector. zero means program error
				System.Diagnostics.Debug.Assert(connection.Connectors.Count > 0);

				if (connection.Connectors.Count == 1)
				{
					if (connection.Connectors[0].UsingNamedBinding)
					{
						ErrorMessage message = new ErrorMessage("Net connected to port '");
						message.AddString(connection.Connectors[0].PortName);
						message.AddString("' of datapath '");
						message.AddString(connection.Connectors[0].DatapathName);
						message.AddString(" ' has only one binding.'");

						warnings.Add(new VerificationWarning(message));
					}
					else
					{
						ErrorMessage message = new ErrorMessage("Net '");
						message.AddString(connection.LocalName);
						message.AddString("' has only one connection");

						warnings.Add(new VerificationWarning(message));
					}
				}

				foreach (system.DatapathConnectorInfo connector in connection.Connectors)
				{		
					Net net = null;

					if(nets.ContainsKey(connection.LocalName))
					{
						net = nets[connection.LocalName] ;
					}
					else
					{
						net = new Net(this, connection.LocalName);
						nets.Add(net.LocalName, net);
					}													

					if (parent.Datapaths.ContainsKey(connector.DatapathName))
					{
						Datapath datapath = parent.Datapaths[connector.DatapathName];

						// mark datapath as present
						datapath.InSystem = true;

						Port port = connector.UsingNamedBinding ? 
								datapath.GetPortByLocalName(connector.PortName) 
							:	datapath.GetPortAtPosition(connector.DeclarationPosition);

						if (port == null)
						{
							if (connector.UsingNamedBinding)
							{
								ErrorMessage message = new ErrorMessage("Port '");
								message.AddString(connector.PortName);
								message.AddString("' does exist on datapath '");
								message.AddEntity(datapath);
								message.AddString("'.");

								errors.Add(new VerificationError(VerificationErrorType.SystemInvalidPort, message));
							}
							else
							{
                                ErrorMessage message = new ErrorMessage("Error in port binding of datapath '");
                                message.AddEntity(datapath);
                                message.AddString("' in system '");
                                message.AddEntity(this);
                                message.AddString("'");

								errors.Add(new VerificationError(VerificationErrorType.SystemInvalidPort, message));
							}
						}
						else if (port is OutPort && net.HasDriver())
						{
							ErrorMessage message = new ErrorMessage();
							message.AddString("Port '");
							message.AddEntity(port);
							message.AddString("' has multiple drivers.");

							errors.Add(new VerificationError(VerificationErrorType.SystemPortHasMultipleDrivers, message));
						}
						else if (port is OutPort)
						{
							net.OutPort = port as OutPort;
						}
						else if (port is InPort)
						{							
							net.InPorts.Add(port as InPort);
						}						
					}
					else if (parent.IPBlocks.ContainsKey(connector.DatapathName))
					{
						IPBlock ipblock = parent.IPBlocks[connector.DatapathName];

						// mark ipblock as present
						ipblock.InSystem = true;

						Port port = connector.UsingNamedBinding ?
							  ipblock.GetPortByLocalName(connector.PortName)
							: ipblock.GetPortAtPosition(connector.DeclarationPosition);

						if (port == null)
						{
							if (connector.UsingNamedBinding)
							{
								ErrorMessage message = new ErrorMessage("Port '");
								message.AddString(connector.PortName);
								message.AddString("' does exist on IP block '");
								message.AddEntity(ipblock);
								message.AddString("'.");

								errors.Add(new VerificationError(VerificationErrorType.SystemInvalidPort, message));
							}
							else
							{
                                ErrorMessage message = new ErrorMessage("Error in port binding of IP block '");
                                message.AddEntity(ipblock);
                                message.AddString("' in system '");
                                message.AddEntity(this);
                                message.AddString("'");

								errors.Add(new VerificationError(VerificationErrorType.SystemInvalidPort, message));
							}
						}
						else if (port is OutPort && net.HasDriver())
						{
							ErrorMessage message = new ErrorMessage();
							message.AddString("Port '");
							message.AddEntity(port);
							message.AddString("' has multiple drivers.");

							errors.Add(new VerificationError(VerificationErrorType.SystemPortHasMultipleDrivers, message));
						}
						else if (port is OutPort)
						{
							net.OutPort = port as OutPort;
						}
						else if (port is InPort)
						{
							net.InPorts.Add(port as InPort);
						}	
					}
					else
					{
						// must be invalid reference
						ErrorMessage message = new ErrorMessage("Unknown datapath or IP block '");
						message.AddString(connector.DatapathName);
						message.AddString("' in system '");
						message.AddEntity(this);
						message.AddString("' at position ");
						message.AddPosition(StartPosition);
						message.AddString(".");

						errors.Add(new VerificationError(VerificationErrorType.SystemUnknownDatapathOrIpBlock, message));
					}
				}
			}
    }
  }
}
